package ch.bfh.mgb.hamilton.simulation.behaviour;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;

import ch.bfh.mgb.hamilton.entities.Food;
import ch.bfh.mgb.hamilton.entities.Robot;
import ch.bfh.mgb.hamilton.gui.Display;
import ch.bfh.mgb.hamilton.simulation.FoodContainer;
import ch.bfh.mgb.hamilton.simulation.Simulation;
import ch.bfh.mgb.hamilton.simulation.SimulationParameters;
import ch.bfh.mgb.hamilton.statistics.Statistics;

public class DiversificationPopulation extends Simulation {

protected Random rand = new Random();
	
	private Statistics statisics;
	private Display display;
	
	private final FoodContainer foodContainer;
	private long currentRobotID = 0;
	// ugly variables...modified per sideEffects...don't tell anyone
	private int starvingToDeath = 0;
	private int overageingToDeath = 0;
	private int newChildren = 0;
	private boolean running = true;
	private int helpernumber = 0;
	
	public DiversificationPopulation(Display display, Statistics statistics) {
		super(display, statistics);
		this.display = display;
		foodContainer = new FoodContainer();
		this.statisics = statistics;
	}
	
	

	@Override
	public void start() {
		// Population list
		running = true;
		List<Robot> pop = new ArrayList<Robot>();

		// Creating the initial population
		pop.addAll(initializePop());

		statisics.init(pop);
		
		int round = 1;
		while (round < SimulationParameters.iterations && running) {
			// Just in case
			Thread.yield();
			if (pop.size() == 0) {
				running = false;
			}
			
			// Reproduction Phase
			List<Robot> newPop = chooseCouples(pop);
			newChildren = newPop.size();
			pop.addAll(newPop);
			
			//reset round values
			for (Robot r : pop){
				r.setCanGiveFood(SimulationParameters.maxHelping);
				r.setNeedFood(0);
			}
			starvingToDeath = 0;
			
			// Overageing Phase
			if (SimulationParameters.overageing) {
				pop = processAgeing(pop);
			}
			
			statisics.processStatistics(round, pop, helpernumber, overageingToDeath, newChildren);
			round++;
		}
		display.performStop();
	}

	@Override
	public List<Robot> initializePop() {
		List<Robot> initialPop = new ArrayList<Robot>();
		for (int i = 1; i <= SimulationParameters.initialPopulationNumberDivers; i++) {
			boolean sex = rand.nextBoolean();
			double reproductionFrequency = SimulationParameters.initialReproductionFrequencyDivers; // rand.nextGaussian()*initialReproductionFrequency;
			int fertility = (int) SimulationParameters.initialFertilityDivers; // (rand.nextGaussian()*initialFertility);
			int timeToLive = rand.nextInt(SimulationParameters.maxTimeToLive);
			Robot r = new Robot(sex, reproductionFrequency, fertility,
					setCurrentRobotID(), timeToLive);
			r.setCanGiveFood(rand.nextInt(SimulationParameters.maxHelping));
			initialPop.add(r);
			
		}
		return initialPop;
	}



	@Override
	public long setCurrentRobotID() {
		currentRobotID++;
		return currentRobotID;
	}



	@Override
	public List<Robot> processAgeing(List<Robot> robots) {
		List<Robot> newGen = new ArrayList<Robot>();
		int killcount = 0;
		
		for(Robot r : robots) {
			if (r.getTimeToLive() > 1) {
				r.setTimeToLive(r.getTimeToLive() - 1);
				newGen.add(r);
			} else {
				killcount ++;
			}
		}
		
		overageingToDeath = killcount;
		
		return newGen;
	}



	@Override
	public Statistics getStatistics() {
		return statisics;
	}



	@Override
	public boolean isRunning() {
		return running;
	}



	@Override
	public void setRunning(boolean running) {
		this.running = running;
	}



	/**
	 * Method for choosing couples of the current population in order to create
	 * new children
	 * 
	 * @param robots
	 *            the current population of robots
	 * @return ArrayList<Robot> the children
	 */
	@Override
	public List<Robot> chooseCouples(List<Robot> robots) {
		List<Robot> robotNewGeneration = new ArrayList<Robot>();
		List<Robot> potentialCouples = new ArrayList<Robot>(robots);
		List<Robot> couples = new ArrayList<Robot>();
		List<Robot> helpers = new ArrayList<Robot>();
		// Create a temporal deep copy of robots in order to choose them and
		// remove them
		// because in one round a robot can only be in one couple
		Collections.copy(potentialCouples, robots);

		Random rand = new Random();
		while (potentialCouples.size() != 0) {
			// Selection of couples on preferences like fertility or reproductionfrequency
			Collections.sort(potentialCouples, new Comparator<Robot>() {
				public int compare(Robot r1, Robot r2) {
					return ((Double)r1.getReproductionFrequency()).compareTo(r2.getReproductionFrequency());
				}
			});
			int robot1ID = potentialCouples.size()-1;
			int robot2ID = potentialCouples.size()-2;
			if (potentialCouples.size() > 1) {

				// main part of the reproduction.
				// the higher the ReproductionFrequency, the better
				if (rand.nextDouble() < robots.get(robot1ID)
						.getReproductionFrequency()
						&& rand.nextDouble() < robots.get(robot2ID)
								.getReproductionFrequency()) {

					List<Robot> childs = createChildren(robots.get(robot1ID),
							robots.get(robot2ID));
					robotNewGeneration.addAll(childs);
					couples.add(potentialCouples.get(robot1ID));
					couples.add(potentialCouples.get(robot2ID));
				} else {
					potentialCouples.get(robot1ID).setCosts(0.5*potentialCouples.get(robot1ID).getFertility()*potentialCouples.get(robot1ID).getReproductionFrequency());
					potentialCouples.get(robot2ID).setCosts(0.5*potentialCouples.get(robot2ID).getFertility()*potentialCouples.get(robot2ID).getReproductionFrequency());
					helpers.add(potentialCouples.get(robot1ID));
					helpers.add(potentialCouples.get(robot2ID));
					
				}
				potentialCouples.remove((robot1ID < robot2ID) ? robot2ID
						: robot1ID);
				potentialCouples.remove((robot1ID < robot2ID) ? robot1ID
						: robot2ID);
			} else {
				helpers.add(potentialCouples.get(0));
				potentialCouples.remove(0);
			}
			helpernumber=helpers.size();
			
		}
		
		for (Robot hr : helpers){
			Collections.sort(couples, new Comparator<Robot>() {
				public int compare(Robot r1, Robot r2) {
					return ((Integer)r1.getNeedFood()).compareTo(r2.getNeedFood());
				}
			});
			Collections.reverse(couples);
			for (Robot pr : couples){
				if (pr.getNeedFood()>pr.getCanGiveFood()){
					int gap = pr.getNeedFood() - pr.getCanGiveFood();
					if (hr.getCanGiveFood()>0){
						int given = (gap>hr.getCanGiveFood())? hr.getCanGiveFood() : gap;
						pr.setNeedFood(pr.getNeedFood()-given);
						hr.setCanGiveFood(hr.getCanGiveFood()-given);
						hr.setProfits(hr.getProfits()+given*2);
					}
				}
			}
		}
		for ( Robot pr : couples){
			if (pr.getNeedFood()<pr.getCanGiveFood()){
				int gap = pr.getNeedFood() - pr.getCanGiveFood();
				boolean firstGeneration = false;
				while (!firstGeneration && gap>0){
					for (Robot ch : robotNewGeneration){
						if (ch.getParent1RobotID()==pr.getRobotID()||ch.getParent2RobotID()==pr.getRobotID()){
							robotNewGeneration.remove(ch);
							gap--;
						}
					}
				}
			}
		}
		
		return robotNewGeneration;
	}
	
	public void whereToHelp(){
		
	}

	public List<Robot> createChildren(Robot robot1, Robot robot2) {
		Random rand = new Random();
		List<Robot> robotChildren = new ArrayList<Robot>();
		double ihPH = SimulationParameters.inheritancePowerOfHigher;
		int f1 = robot1.getFertility();
		int f2 = robot2.getFertility();
		double mutation = rand.nextGaussian()*SimulationParameters.mutationRateFertility;
		//take care of eventually higher fertility of a robot
		double ih = (f1<f2) ? ihPH*f2+(1-ihPH)*f1 : ihPH*f1+(1-ihPH)*f2;
		//As a couple, a minimum of 1 child is required
		int childNumber = (int) ((1>ih+mutation) ? 1 : ih+mutation);
		robot1.setNeedFood( (int) (childNumber/2));
		robot2.setNeedFood( (int) (childNumber/2));
		
		//set properties for each child
		for (int i = 1; i <= childNumber; i++) {
			boolean sex = true;
			double fr1 = robot1.getReproductionFrequency();
			double fr2 = robot2.getReproductionFrequency();
			double mutationR = rand.nextGaussian()*SimulationParameters.mutationRateReproduction;
			double rf = (fr1<fr2) ? ihPH*fr2+(1-ihPH)*fr1 : ihPH*fr1+(1-ihPH)*fr2;
			double reproductionFrequency = (int) ((1>rf+mutationR) ? 1 : rf+mutationR);
			
			int f1ch = robot1.getFertility();
			int f2ch = robot2.getFertility();
			double mutationch = rand.nextGaussian()*SimulationParameters.mutationRateFertility;
			//take care of eventually higher fertility of a robot
			double ihch = (f1ch<f2ch) ? ihPH*f2ch+(1-ihPH)*f1ch : ihPH*f1ch+(1-ihPH)*f2ch;
			// a minimum of 0 child is required
			int fertility = (int) ((0>ihch+mutationch) ? 0 : ihch+mutationch);
			
			//int timeToLive = rand.nextInt(SimulationParameters.maxTimeToLive);
			//Assuming all robots reach their normal end of life
			int timeToLive = rand.nextInt(SimulationParameters.maxTimeToLive);
			Robot r = new Robot(sex, reproductionFrequency, fertility,
					setCurrentRobotID(), timeToLive, processNewFoodBehaviour(robot1, robot2));
			r.setCanGiveFood(SimulationParameters.maxHelping);
			r.setParent1RobotID(robot1.getRobotID());
			r.setParent2RobotID(robot2.getRobotID());
			robotChildren.add(r);
			
			//Each parent receives a profit of a half per child
			 robot1.setProfits(robot1.getProfits()+0.5);
			 robot2.setProfits(robot2.getProfits()+0.5);
		}
		return robotChildren;
	}
	
	private int[] processNewFoodBehaviour(Robot r1, Robot r2) {
		int[] beh = new int[SimulationParameters.foodIterations];
		int genesFromR1 = new Double(((double)SimulationParameters.foodIterations) 
			/ ((r1.getFoodCount() + 1.0) + (r2.getFoodCount() + 1.0)) 
			* (r1.getFoodCount() + 1.0)).intValue();

		System.arraycopy(Arrays.copyOfRange(r1.getFoodBehaviour(), 0, genesFromR1)
				, 0, beh, 0, genesFromR1);
		System.arraycopy(Arrays.copyOfRange(r2.getFoodBehaviour(), genesFromR1, SimulationParameters.foodIterations)
				, 0, beh, genesFromR1, SimulationParameters.foodIterations - genesFromR1);
		
		return beh;
	}
	
	public List<Robot> chosenFoodSearchers(List<Robot> pop, int count) {
		List<Robot> chosen = new ArrayList<Robot>();
		Collections.sort(pop, new Comparator<Robot>() {
			public int compare(Robot r1, Robot r2) {
				//return ((Integer)r1.getFertility()).compareTo(r2.getFertility());
				return ((Double)r1.getFoodCount()).compareTo(r2.getFoodCount());
			}
		});
		//Collections.reverse(pop);
		if (count > pop.size()) {
			return pop;
		}
		for (; count > 0; count--) {
			chosen.add(pop.get(count - 1));
		}
		return chosen;
	}
}
